package com.jltx.xq.simplewatch.db;

import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.util.ArrayList;

/**
 * @author jltxseo
 *         Created by junlintianxia on 2017/08/23.
 */

public class SimpleWatchProvider extends ContentProvider{

    static final int NEWS_RECORD = 1000;
    static final int NEWS_RECORD_ID = 1001;

    static final int NEW_STATE = 2000;

    private SQLiteOpenHelper dbOpenHelper;
    protected SQLiteDatabase mDb;
    private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
    /**
     * Maximum number of operations allowed in a batch between yield points.
     */
    private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500;
    private static final int SLEEP_AFTER_YIELD_DELAY = 4000;

    private static final UriMatcher matcher ;
    static {
        matcher = new UriMatcher(UriMatcher.NO_MATCH);
        matcher.addURI(SimpleWatchContract.AUTHORITY,SimpleWatchContract.BASE_NEWS_RECORD_PATH,NEWS_RECORD);
        matcher.addURI(SimpleWatchContract.AUTHORITY,SimpleWatchContract.BASE_NEWS_RECORD_PATH+"/#",NEWS_RECORD_ID);
        matcher.addURI(SimpleWatchContract.AUTHORITY,SimpleWatchContract.BASE_NEWS_STATE_PATH,NEW_STATE);
    }
    @Override
    public boolean onCreate() {
        Context context = getContext();
        dbOpenHelper = new DatabaseHelper(context);
        mDb = dbOpenHelper.getWritableDatabase();
        if(mDb != null){
            return true;
        }
        return false;
    }

    /**
     * 是否批量操作
     * @return
     */
    private boolean applyingBatch() {
        return mApplyingBatch.get() != null && mApplyingBatch.get();
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        final int match = matcher.match(uri);
        String table = getMatchTable(uri,match);
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        qb.setTables(table);
        switch (match) {
            case NEWS_RECORD_ID:
                qb.appendWhere(SimpleWatchContract.NewsRecordColumns.ID + "=" + uri.getPathSegments().get(1));
                break;
        }

        String limit = uri.getQueryParameter("limit");
        String groupBy = uri.getQueryParameter("groupBy");
        return qb.query(mDb,projection,selection,selectionArgs,groupBy,null,sortOrder,limit);
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        Uri result = null;
        boolean applyingBatch = applyingBatch();
        if(!applyingBatch){
            mDb.beginTransaction();
            try {
                result = insertTransaction(uri,contentValues);
                mDb.setTransactionSuccessful();
            }finally {
                mDb.endTransaction();
            }
        }else {
            result = insertTransaction(uri,contentValues);
        }
        getContext().getContentResolver().notifyChange(result, null);
        return result;
    }

    @Override
    public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
        int numValues = values.length;
        mDb.beginTransaction();
        try {
            for (int i = 0; i < numValues; i++) {
                 insertTransaction(uri,values[i]);
                mDb.yieldIfContendedSafely();
            }
            mDb.setTransactionSuccessful();
        }finally {
            mDb.endTransaction();
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return numValues;
    }

    Uri insertTransaction(@NonNull Uri uri, @Nullable ContentValues contentValues){
        final int match = matcher.match(uri);
        String table = getMatchTable(uri,match);
        long id = mDb.insert(table,null,contentValues);
        Uri result = ContentUris.withAppendedId(uri,id);
        return result;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        int count = 0;
        boolean applyingBatch = applyingBatch();
        if(!applyingBatch){
            mDb.beginTransaction();
            try {
                count = deleteTransaction(uri,selection,selectionArgs);
                mDb.setTransactionSuccessful();
            }finally {
                mDb.endTransaction();
            }
        }else {
            count = deleteTransaction(uri,selection,selectionArgs);
        }
        getContext().getContentResolver().notifyChange(uri,null);
        return count;
    }

    int deleteTransaction(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs){
        final int match = matcher.match(uri);
        String table = getMatchTable(uri,match);
        switch (match) {
            case NEWS_RECORD_ID:
                if(selection == null){
                    selection = SimpleWatchContract.NewsRecordColumns.ID+" =?";
                }else {
                    selection += " "+SimpleWatchContract.NewsRecordColumns.ID+" =?";
                }
                if(selectionArgs != null){
                    String[] newArgs = new String[selectionArgs.length + 1];
                    for(int i=0; i < selectionArgs.length;i++){
                        newArgs[i] = selectionArgs[i];
                    }
                    newArgs[selectionArgs.length] = uri.getPathSegments().get(1);
                    selectionArgs = newArgs;
                }else {
                    selectionArgs = new String[]{uri.getPathSegments().get(1)};
                }
                break;
        }
        return mDb.delete(table, selection, selectionArgs);
    }


    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {
        int count = 0;
        boolean applyingBatch = applyingBatch();
        if(!applyingBatch){
            mDb.beginTransaction();
            try {
                count = updateTransaction(uri,contentValues,selection,selectionArgs);
                mDb.setTransactionSuccessful();
            }finally {
                mDb.endTransaction();
            }
        }else {
            count = updateTransaction(uri,contentValues,selection,selectionArgs);
        }
        getContext().getContentResolver().notifyChange(uri,null);
        return count;
    }

    int updateTransaction(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {
        final int match = matcher.match(uri);
        String table = getMatchTable(uri,match);
        switch (match) {
            case NEWS_RECORD_ID:
                if(selection == null){
                    selection = SimpleWatchContract.NewsRecordColumns.ID+" =?";
                }else {
                    selection += " "+SimpleWatchContract.NewsRecordColumns.ID+" =?";
                }
                if(selectionArgs != null){
                    String[] newArgs = new String[selectionArgs.length + 1];
                    for(int i=0; i < selectionArgs.length;i++){
                        newArgs[i] = selectionArgs[i];
                    }
                    newArgs[selectionArgs.length] = uri.getPathSegments().get(1);
                    selectionArgs = newArgs;
                }else {
                    selectionArgs = new String[]{uri.getPathSegments().get(1)};
                }
                break;
        }
        return mDb.update(table,contentValues,selection,selectionArgs);
    }

    private String getMatchTable(Uri uri,int match) {
        String table = null;
        switch (match){
            case NEWS_RECORD_ID:
                table = SimpleWatchContract.TABLE_NEWS_RECORD_NAME;
                break;
            case NEWS_RECORD:
                table = SimpleWatchContract.TABLE_NEWS_RECORD_NAME;
                break;
            case NEW_STATE:
                table = SimpleWatchContract.TABLE_NEWS_STATE_NAME;
                break;
            default:
                throw new IllegalArgumentException("Unknown URI"+uri);
        }
        return table;
    }

    @NonNull
    @Override
    public ContentProviderResult[] applyBatch(@NonNull ArrayList<ContentProviderOperation> operations) throws OperationApplicationException {
        int ypCount = 0;
        int opCount = 0;
        mDb.beginTransaction();
        try {
            mApplyingBatch.set(true);
            final int numOperations = operations.size();
            final ContentProviderResult[] results = new ContentProviderResult[numOperations];
            for (int i = 0; i < numOperations; i++) {
                if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) {
                    throw new OperationApplicationException(
                            "Too many content provider operations between yield points. "
                                    + "The maximum number of operations per yield point is "
                                    + MAX_OPERATIONS_PER_YIELD_POINT, ypCount);
                }
                final ContentProviderOperation operation = operations.get(i);
                if (i > 0 && operation.isYieldAllowed()) {
                    opCount = 0;
                    if (mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)) {
                        ypCount++;
                    }
                }
                results[i] = operation.apply(this, results, i);
            }
            mDb.setTransactionSuccessful();
            return results;
        } finally {
            mApplyingBatch.set(false);
            mDb.endTransaction();
        }
    }

    final private  static class DatabaseHelper extends SQLiteOpenHelper{
        static final String DATABASE_NAME = "simplewatch.db";
        static final int DATABASE_VERSION = 2;

        public DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
            if(Build.VERSION.SDK_INT > 15){
                setWriteAheadLoggingEnabled(true);
            }
        }

        @Override
        public void onCreate(SQLiteDatabase sqLiteDatabase) {
            onUpgrade(sqLiteDatabase, 0, DATABASE_VERSION);
        }

        @Override
        public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
            for (int version = oldVersion + 1; version <= newVersion; version++) {
                upgradeTo(sqLiteDatabase, version);
            }
        }

        /**
         * 数据库的创建和升级都进该函数
         * Upgrade database from (version - 1) to version.
         */
        private void upgradeTo(SQLiteDatabase db, int version) {
            switch (version) {
                case 1:
                    db.execSQL(SQLS.CREATE_NEWS_RECORD_TABLE);
                    break;
                case 2:
                    db.execSQL(SQLS.CREATE_NEWS_STATE_TABLE);
                    break;
                default:
                    throw new IllegalStateException("Don't know how to upgrade to " + version);
            }
        }

        static class SQLS{
            static final String CREATE_NEWS_RECORD_TABLE =
                    "CREATE TABLE IF NOT EXISTS " + SimpleWatchContract.TABLE_NEWS_RECORD_NAME
                            + "("
                            + SimpleWatchContract.NewsRecordColumns.ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
                            + SimpleWatchContract.NewsRecordColumns.CHANNEL_CODE + " TEXT,"
                            + SimpleWatchContract.NewsRecordColumns.PAGE + " INTEGER,"
                            + SimpleWatchContract.NewsRecordColumns.TIME + " INTEGER,"
                            + SimpleWatchContract.NewsRecordColumns.DATA + " TEXT"
                            + ")";

            static final String CREATE_NEWS_STATE_TABLE =
                    "CREATE TABLE IF NOT EXISTS " + SimpleWatchContract.TABLE_NEWS_STATE_NAME
                            + "("
                            + SimpleWatchContract.NewsStateColumns.KEY + " TEXT,"
                            + SimpleWatchContract.NewsStateColumns.CHANNEL + " TEXT,"
                            + SimpleWatchContract.NewsStateColumns.IS_READ + " INTEGER,"
                            + SimpleWatchContract.NewsStateColumns.IS_DELETE + " INTEGER,"
                            + "constraint url_channel PRIMARY KEY ("+SimpleWatchContract.NewsStateColumns.KEY +","+SimpleWatchContract.NewsStateColumns.CHANNEL+")"
                            + ")";
        }
    }
}
